package zserver

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"time"

	"gitee.com/youkelike/ziam/zmiddleware"
	"gitee.com/youkelike/ziam/zutils/response"
	"gitee.com/youkelike/ziam/zutils/version"
	"gitee.com/youkelike/zlog"
	"github.com/gin-contrib/pprof"
	"github.com/gin-gonic/gin"
	ginprometheus "github.com/zsais/go-gin-prometheus"
	"golang.org/x/sync/errgroup"
)

type GenericAPIServer struct {
	middlewares []string
	// SecureServingInfo holds configuration of the TLS server.
	SecureServingInfo *SecureServingInfo

	// InsecureServingInfo holds configuration of the insecure HTTP server.
	InsecureServingInfo *InsecureServingInfo

	// ShutdownTimeout is the timeout used for server shutdown. This specifies the timeout before server
	// gracefully shutdown returns.
	ShutdownTimeout time.Duration

	*gin.Engine
	healthz         bool
	enableMetrics   bool
	enableProfiling bool
	// wrapper for gin.Engine

	insecureServer, secureServer *http.Server
}

func (s *GenericAPIServer) PrepareRun() {
	s.Setup()
	s.InstallMiddlewares()
	s.InstallAPIs()
}

func (s *GenericAPIServer) Setup() {
	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
		zlog.Infow("%-6s %-s --> %s (%d handlers)", httpMethod, absolutePath, handlerName, nuHandlers)
	}
}

func (s *GenericAPIServer) InstallMiddlewares() {
	// // necessary middlewares
	s.Use(zmiddleware.RequestID())
	s.Use(zmiddleware.Context())

	// install custom middlewares
	for _, m := range s.middlewares {
		mw, ok := zmiddleware.Middlewares[m]
		if !ok {
			zlog.Warnw("can not find middleware: %s", m)

			continue
		}

		zlog.Infow("install middleware: %s", m)
		s.Use(mw)
	}

	// install metric handler
	if s.enableMetrics {
		prometheus := ginprometheus.NewPrometheus("gin")
		prometheus.Use(s.Engine)
	}

	// install pprof handler
	if s.enableProfiling {
		pprof.Register(s.Engine)
	}
}

func (s *GenericAPIServer) InstallAPIs() {
	// install healthz handler
	if s.healthz {
		s.GET("/healthz", func(c *gin.Context) {
			response.Write(c, nil, map[string]string{"status": "ok"})
		})
	}

	s.GET("/version", func(c *gin.Context) {
		response.Write(c, nil, version.Get())
	})
}

func (s *GenericAPIServer) Run() error {
	s.insecureServer = &http.Server{
		Addr:    s.InsecureServingInfo.Address,
		Handler: s, // s 内嵌了 gin.Engin，可以作为 http.ServerMux 使用
	}

	s.secureServer = &http.Server{
		Addr:    s.SecureServingInfo.Address(),
		Handler: s, // s 内嵌了 gin.Engin，可以作为 http.ServerMux 使用
	}

	var eg errgroup.Group

	eg.Go(func() error {
		zlog.Infow("insecure server at: %s", s.InsecureServingInfo.Address)
		if err := s.insecureServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			zlog.Fatalw(err.Error())
			return err
		}

		zlog.Infow("insecure server stopped")
		return nil
	})

	eg.Go(func() error {
		key, cert := s.SecureServingInfo.CertKey.KeyFile, s.SecureServingInfo.CertKey.CertFile
		if cert == "" || key == "" || s.SecureServingInfo.BindPort == 0 {
			return nil
		}

		zlog.Infow("secure server at: %s", s.SecureServingInfo.Address())
		if err := s.secureServer.ListenAndServeTLS(cert, key); err != nil && !errors.Is(err, http.ErrServerClosed) {
			zlog.Fatalw(err.Error())
			return err
		}

		zlog.Infow("secure server stopped")
		return nil
	})

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if s.healthz {
		if err := s.ping(ctx); err != nil {
			return err
		}
	}

	if err := eg.Wait(); err != nil {
		zlog.Fatalw(err.Error())
	}

	return nil
}

func (s *GenericAPIServer) Close() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := s.insecureServer.Shutdown(ctx); err != nil {
		zlog.Warnw("shutdown insecure server failed: %s", err.Error())
	}

	if err := s.secureServer.Shutdown(ctx); err != nil {
		zlog.Warnw("shutdown secure server failed: %s", err.Error())
	}
}

// 有失败重试和超时控制的 ping
func (s *GenericAPIServer) ping(ctx context.Context) error {
	url := fmt.Sprintf("http://%s/healthz", s.InsecureServingInfo.Address)
	if strings.Contains(s.InsecureServingInfo.Address, "0.0.0.0") {
		url = fmt.Sprintf("http://127.0.0.1:%s/healthz", strings.Split(s.InsecureServingInfo.Address, ":")[1])
	}

	for {
		// Change NewRequest to NewRequestWithContext and pass context it
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		if err != nil {
			return err
		}

		resp, err := http.DefaultClient.Do(req)
		if err == nil && resp.StatusCode == http.StatusOK {
			zlog.Infow("The router has been deployed successfully.")

			resp.Body.Close()
			return nil
		}

		// Sleep for a second to continue the next ping.
		zlog.Infow("Waiting for the router, retry in 1 second.")
		time.Sleep(1 * time.Second)

		select {
		case <-ctx.Done():
			zlog.Fatalw("can not ping http server within the specified time interval.[%s]", url)
		default:
		}
	}
}
